////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNMemory/inc/WNAllocation.h"
#include "WNScripting/inc/WNLValue.h"
#include "WNScripting/inc/WNExpression.h"
#include "WNScripting/inc/WNIdentifierMap.h"
#include "WNScripting/inc/WNScriptingConfig.h"
#include "WNScripting/inc/WNLLVMHelpers.h"

using namespace WNScripting;

WNLValue::WNLValue(WN_SIZE_T _startLine, WN_SIZE_T _endLine, WN_SIZE_T _startChar, WN_SIZE_T _endChar, WNIdentifierElement _identifier):
    WNParseNode(_startLine, _endLine, _startChar, _endChar),
    mIdentifier(_identifier),
    mExpression(WN_NULL),
    mLValue(WN_NULL) {
    mType = eWNLValueIdentifier;
}

WNLValue::WNLValue(WN_SIZE_T _startLine, WN_SIZE_T _endLine, WN_SIZE_T _startChar, WN_SIZE_T _endChar,  WNLValue* _lval, WNExpression* _expr):
    WNParseNode(_startLine, _endLine, _startChar, _endChar),
    mLValue(_lval),
    mExpression(_expr) {
    mType = eWNLValueArrayAccess;
}

WNLValue::WNLValue(WN_SIZE_T _startLine,
                   WN_SIZE_T _endLine,
                   WN_SIZE_T _startChar,
                   WN_SIZE_T _endChar,
                   WNLValue* _lVal,
                   WNIdentifierElement _identifier):
    WNParseNode(_startLine, _endLine, _startChar, _endChar),
    mLValue(_lVal),
    mIdentifier(_identifier),
    mExpression(WN_NULL),
    mType(eWNLValueStructAccess) {

}

WNLValue::~WNLValue() {
    switch(mType) {
        case eWNLValueArrayAccess:
            WN_DELETE(mLValue);
            WN_DELETE(mExpression);

            break;
        case eWNLValueStructAccess:
            WN_DELETE(mExpression);
    }
}

WNTypeElement WNLValue::GetOutType() {
    WN_DEBUG_ASSERT(mOptimized);

    return(mOutType);
}

llvm::Value* WNLValue::OptimizeAndGenerate(WNScriptingEngineData& _scriptingData, WNFunctionDecl* _functionDecl) {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_scriptingData.mBuilder);

    WNParseNode::OptimizeAndGenerate(_scriptingData, _functionDecl);
    switch(mType) {
        case eWNLValueIdentifier: {
                const WNScriptVariable& var = _scriptingData.mScopedVariableList->GetVariable(mIdentifier);
                if (!WNIsValid(var)){
                    mFail = "Invalid Variable";
                    return(WN_NULL);
                }
                mOutType = var.mType;
                return(var.mLlvmAlloca);
            }
            break;
        case eWNLValueArrayAccess: {
                llvm::Type* t = GET_DATA_LAYOUT(_scriptingData.mExecutionEngine)->GET_INT_PTR_TYPE(llvm::getGlobalContext());
                llvm::Value* offset = mExpression->OptimizeAndGenerate(_scriptingData, _functionDecl);
                llvm::Value * ptr = mLValue->OptimizeAndGenerate(_scriptingData, _functionDecl);
                llvm::Value* outPointer = builder->CreateLoad(ptr, WN_FALSE, "GetValueOut");

                ptr = builder->CreatePointerCast(outPointer, llvm::PointerType::getUnqual(_scriptingData.mTypeManager->GetType(eWNTypeInt)->mLlvmType));
                WNGenerateLLVMCast castFunc = _scriptingData.mTypeManager->GetLLVMCastGenerator(_scriptingData.mTypeManager->GetType(eWNTypeInt), mExpression->GetType());
                if (castFunc == WN_NULL) {
                    mFail = "Failed to generate cast for array access";
                    return(WN_NULL);
                }
                offset = castFunc(offset, *builder);
                
 
#ifdef WN_SCRIPTING_ENABLE_BOUNDS_CHECKING
                
                llvm::Value* lookVal = builder->CreateLoad(ptr);
                llvm::Value* cmp = builder->CreateICmpULT(offset, lookVal, "array bounds check");
                
                llvm::BasicBlock* outOfBounds = llvm::BasicBlock::Create(llvm::getGlobalContext(), "arrayOutOfBounds", _scriptingData.mScopedVariableList->GetCurrentFunction()->mLLVMFunction); // NEED TO PASS FUNCTION DOWN (probably use script data instead of builder :|
                llvm::BasicBlock* boundsOk = llvm::BasicBlock::Create(llvm::getGlobalContext(), "arrayInBounds", _scriptingData.mScopedVariableList->GetCurrentFunction()->mLLVMFunction);
                
                builder->CreateCondBr(cmp, boundsOk, outOfBounds);
                builder->SetInsertPoint(outOfBounds);
                llvm::Value* nullVal = llvm::ConstantInt::get(t, 0);
                nullVal = builder->CreateIntToPtr(nullVal, _scriptingData.mTypeManager->GetType(eWNTypeInt)->mLlvmType->getPointerTo(0));
                llvm::Value* crashVal = builder->CreateLoad(nullVal, WN_TRUE, "");
                builder->CreateStore(crashVal, nullVal, WN_TRUE);
                builder->CreateBr(boundsOk);
                builder->SetInsertPoint(boundsOk);
#endif
                WNCreateConstantFunction constFunc = _scriptingData.mTypeManager->GetLLVMCreateConstant(_scriptingData.mTypeManager->GetType(eWNTypeInt));
                WN_INT32 constOffset = WNLLVMGetTypesPerType(_scriptingData, _scriptingData.mTypeManager->GetLLVMType(_scriptingData.mTypeManager->GetType(eWNTypeInt)), _scriptingData.mTypeManager->GetLLVMType(_scriptingData.mTypeManager->GetSubType(mLValue->mOutType)));

                llvm::Value* constVal = builder->CreateAdd(constFunc(_scriptingData, (WN_CHAR*)&constOffset), offset);
                llvm::Value* retPtr = builder->CreateGEP(outPointer, constVal);
                WN_DEBUG_ASSERT(_scriptingData.mTypeManager->IsArrayType(mLValue->GetOutType()));
                mOutType = _scriptingData.mTypeManager->GetSubType(mLValue->GetOutType());
                
                return(retPtr);
            }
        case eWNLValueStructAccess: {
                llvm::Value* v = mLValue->OptimizeAndGenerate(_scriptingData, _functionDecl);
                if (!v) {
                    return(WN_NULL);
                }
                
                WNTypeElement elem = mLValue->GetOutType();
                if (!_scriptingData.mTypeManager->IsStructType(elem)) {
                    mFail = "Error! Cannot assign to a member of a not struct type\n";
                    return(WN_NULL);
                }
                v = builder->CreateLoad(v, WN_FALSE);

                WNTypeElement targetType;
                WN_SIZE_T location;
                if (!_scriptingData.mTypeManager->GetStructValueLocation(elem, mIdentifier, targetType, location)) {
                    mFail = "Error, cannot find the correct Identifier\n";
                }

                llvm::Value* gepValues[2] = {llvm::ConstantInt::get(builder->getInt32Ty(), 0), llvm::ConstantInt::get(builder->getInt32Ty(), location) };
#ifdef _WN_ANDROID
                llvm::Value* oVal = builder->CreateGEP(v, &gepValues[0], &gepValues[1]);
#else
                llvm::Value* oVal = builder->CreateGEP(v, llvm::makeArrayRef(gepValues, 2));
#endif
                mOutType = targetType;
                return(oVal);
            }
            break;
    }
    return(WN_NULL);
}
